/* -------------------------------------------------------------------------
 *
 * contrib/sepgsql/schema.c
 *
 * 与模式对象对应的例程
 *
 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/dependency.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "commands/seclabel.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "sepgsql.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/snapmgr.h"

/*
 * sepgsql_schema_post_create
 *
 * 此例程为新定义的
 * schema 分配默认的安全标签。
 */
void sepgsql_schema_post_create(Oid fc_namespaceId)
{
	Relation	fc_rel;
	ScanKeyData fc_skey;
	SysScanDesc fc_sscan;
	HeapTuple	fc_tuple;
	char	   *fc_tcontext;
	char	   *fc_ncontext;
	const char *fc_nsp_name;
	ObjectAddress fc_object;
	Form_pg_namespace fc_nspForm;
	StringInfoData fc_audit_name;

	/*
	 * 在我们创建一个新的 schema 对象时计算默认的安全标签
	 * 在工作数据库下。
	 *
	 * XXX - libselinux的即将发布的版本支持使用对象名称
	 * 处理默认安全标签的特殊处理；例如在 "pg_temp" schema 上的特殊
	 * 标签。
	 */
	fc_rel = table_open(NamespaceRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey,
				Anum_pg_namespace_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_namespaceId));

	fc_sscan = systable_beginscan(fc_rel, NamespaceOidIndexId, true,
							   SnapshotSelf, 1, &fc_skey);
	fc_tuple = systable_getnext(fc_sscan);
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "could not find tuple for namespace %u", fc_namespaceId);

	fc_nspForm = (Form_pg_namespace) GETSTRUCT(fc_tuple);
	fc_nsp_name = NameStr(fc_nspForm->nspname);
	if (strncmp(fc_nsp_name, "pg_temp_", 8) == 0)
		fc_nsp_name = "pg_temp";
	else if (strncmp(fc_nsp_name, "pg_toast_temp_", 14) == 0)
		fc_nsp_name = "pg_toast_temp";

	fc_tcontext = sepgsql_get_label(DatabaseRelationId, MyDatabaseId, 0);
	fc_ncontext = sepgsql_compute_create(sepgsql_get_client_label(),
									  fc_tcontext,
									  SEPG_CLASS_DB_SCHEMA,
									  fc_nsp_name);

	/*
	 * 检查 db_schema:{create}
	 */
	initStringInfo(&fc_audit_name);
	appendStringInfo(&fc_audit_name, "%s", quote_identifier(fc_nsp_name));
	sepgsql_avc_check_perms_label(fc_ncontext,
								  SEPG_CLASS_DB_SCHEMA,
								  SEPG_DB_SCHEMA__CREATE,
								  fc_audit_name.data,
								  true);
	systable_endscan(fc_sscan);
	table_close(fc_rel, AccessShareLock);

	/*
	 * 为新过程分配默认安全标签
	 */
	fc_object.classId = NamespaceRelationId;
	fc_object.objectId = fc_namespaceId;
	fc_object.objectSubId = 0;
	SetSecurityLabel(&fc_object, SEPGSQL_LABEL_TAG, fc_ncontext);

	pfree(fc_ncontext);
	pfree(fc_tcontext);
}

/*
 * sepgsql_schema_drop
 *
 * 它检查删除提供的 schema 对象的权限。
 */
void sepgsql_schema_drop(Oid fc_namespaceId)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;

	/*
	 * 检查 db_schema:{drop} 权限
	 */
	fc_object.classId = NamespaceRelationId;
	fc_object.objectId = fc_namespaceId;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_SCHEMA,
							SEPG_DB_SCHEMA__DROP,
							fc_audit_name,
							true);
	pfree(fc_audit_name);
}

/*
 * sepgsql_schema_relabel
 *
 * 它检查通过 `seclabel' 重新标记提供的 schema 的权限。
 */
void sepgsql_schema_relabel(Oid fc_namespaceId, const char *fc_seclabel)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;

	fc_object.classId = NamespaceRelationId;
	fc_object.objectId = fc_namespaceId;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	/*
	 * 检查 db_schema:{setattr relabelfrom} 权限
	 */
	sepgsql_avc_check_perms(&fc_object,
							SEPG_CLASS_DB_SCHEMA,
							SEPG_DB_SCHEMA__SETATTR |
							SEPG_DB_SCHEMA__RELABELFROM,
							fc_audit_name,
							true);

	/*
	 * 检查 db_schema:{relabelto} 权限
	 */
	sepgsql_avc_check_perms_label(fc_seclabel,
								  SEPG_CLASS_DB_SCHEMA,
								  SEPG_DB_SCHEMA__RELABELTO,
								  fc_audit_name,
								  true);
	pfree(fc_audit_name);
}

/*
 * sepgsql_schema_check_perms
 *
 * 工具例程以检查 db_schema:{xxx} 权限
 */
static bool fc_check_schema_perms(Oid fc_namespaceId, uint32 fc_required, bool fc_abort_on_violation)
{
	ObjectAddress fc_object;
	char	   *fc_audit_name;
	bool		fc_result;

	fc_object.classId = NamespaceRelationId;
	fc_object.objectId = fc_namespaceId;
	fc_object.objectSubId = 0;
	fc_audit_name = getObjectIdentity(&fc_object, false);

	fc_result = sepgsql_avc_check_perms(&fc_object,
									 SEPG_CLASS_DB_SCHEMA,
									 fc_required,
									 fc_audit_name,
									 fc_abort_on_violation);
	pfree(fc_audit_name);

	return fc_result;
}

/* db_schema:{setattr} 权限 */
void sepgsql_schema_setattr(Oid fc_namespaceId)
{
	fc_check_schema_perms(fc_namespaceId, SEPG_DB_SCHEMA__SETATTR, true);
}

/* db_schema:{search} 权限 */
bool sepgsql_schema_search(Oid fc_namespaceId, bool fc_abort_on_violation)
{
	return fc_check_schema_perms(fc_namespaceId,
							  SEPG_DB_SCHEMA__SEARCH,
							  fc_abort_on_violation);
}

void sepgsql_schema_add_name(Oid fc_namespaceId)
{
	fc_check_schema_perms(fc_namespaceId, SEPG_DB_SCHEMA__ADD_NAME, true);
}

void sepgsql_schema_remove_name(Oid fc_namespaceId)
{
	fc_check_schema_perms(fc_namespaceId, SEPG_DB_SCHEMA__REMOVE_NAME, true);
}

void sepgsql_schema_rename(Oid fc_namespaceId)
{
	fc_check_schema_perms(fc_namespaceId,
					   SEPG_DB_SCHEMA__ADD_NAME |
					   SEPG_DB_SCHEMA__REMOVE_NAME,
					   true);
}
